using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using CodePlex.DnD4eCampaignManager.DnD4e;
using CodePlex.DnD4eCampaignManager.DnD4e.Enums;
using CodePlex.DnD4eCampaignManager.Repositories;
using CodePlex.DnD4eCampaignManager.Repositories.XmlRepositories;
using NUnit.Framework;
using WatiN.Core;

namespace CodePlex.DnD4eCampaignManager.CampaignManagerTests.ServiceTests
{
    [TestFixture, Ignore("These tests were written to learn how to use the DDICompendium.  They are not part of the full test suite.  They are not even independant.  Don't run them unless you know what you are doing...")]
    public class DungeonsAndDragonsInsiderCompendiumLearningTests
    {
        private const RegexOptions REGEX_OPTIONS = RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline;
        private IE _ie;
        private IMonsterRepository _monsterRepository;

        [Test]
        public void CanFindAllTheMonsterLinks()
        {
            const string monsterToSearchFor = "dragon";
            Dictionary<int, string> monsterIds = GetMonsterIds(monsterToSearchFor);
            WriteMonsterIdsFromPageToOutputWindow(monsterIds);
        }

        [Test]
        public void CanGetFreeMonsterTextByName()
        {
            string monsterText = GetMonsterText(300);
            Assert.IsTrue(monsterText.Contains("Kobold Dragonshield"));
        }

        [Test]
        public void CanGetPayMonsterTextByName()
        {
            string monsterText = GetMonsterText(1441);
            Assert.IsTrue(monsterText.Contains("Adult Pact Dragon"));
        }

        [Test]
        public void ConvertPreviouslyDownloadedFilesToMonsterObjectsAndSaveThem()
        {
            int total = 0, successes = 0, failures = 0;
            _monsterRepository = new MonsterXmlRepository();
            foreach ( string file in Directory.GetFiles("MonsterHtml","*.htm") )
            {
                total++;
                try
                {
                    ImportHtmlAndSaveFile(file);
                    successes++;
                }
                catch ( Exception  e )
                {
                    Console.Out.WriteLine("Conversion failed for: {0} {1}", file, e.Message);
                    failures++;
                }
            }
            Console.Out.WriteLine("total = {0}", total);
            Console.Out.WriteLine("successes = {0}", successes);
            Console.Out.WriteLine("failures = {0}", failures);
            Assert.AreEqual(total, successes + failures);
            Assert.AreEqual(0, failures);
        }

        [Test]
        public void GetAllMonsterHtmlFiles()
        {
            Dictionary<int, string> monsterIds = new Dictionary<int, string>();
            using ( StreamReader file = File.OpenText("monsterIds.txt") )
            {
                while ( !file.EndOfStream )
                {
                    string line = file.ReadLine();
                    if ( !string.IsNullOrEmpty(line) )
                    {
                        string[] lineParts = line.Split('|');
                        monsterIds.Add(int.Parse(lineParts[0]), lineParts[1]);
                    }
                }
            }
            foreach ( KeyValuePair<int, string> monster in monsterIds )
            {
                Console.Out.WriteLine("monster = {0}", monster);
                string monsterText = GetMonsterText(monster.Key);
                string path = string.Format("MonsterHtml\\{0}.htm", monster.Value).Replace("\"", string.Empty);
                File.WriteAllText(path, monsterText);
//                Console.Out.WriteLine("path = {0}", path);
            }
        }

        [Test]
        public void GetAllMonsterIds()
        {
            GetBrowser();
            NavigateToCompendium();
            SetSearchModeToMonsters();
            _ie.Image(Find.ById("view_all_ball")).Click();
            Thread.Sleep(10000);
            WaitForSearchToComplete();
            Set100ResultsPerPage();
            int expectedMonsterCount = GetExpectedMonsterCount();
            Dictionary<int, string> monsterIds = GetMonsterIds();
            if ( expectedMonsterCount != monsterIds.Count )
            {
                //Just try again
                Thread.Sleep(2000);
                monsterIds = GetMonsterIds();
            }
            Assert.AreEqual(expectedMonsterCount, monsterIds.Count);
            StringBuilder stringBuilder = new StringBuilder();
            foreach ( KeyValuePair<int, string> id in monsterIds )
            {
                stringBuilder.AppendLine(id.Key + "|" + id.Value);
            }
            File.WriteAllText("monsterIds.txt", stringBuilder.ToString());
        }

        [Test]
        public void GetMonsterFiles()
        {
            const string monsterToSearchFor = "kobold";
            Dictionary<int, string> monsterIds = GetMonsterIds(monsterToSearchFor);
            IMonsterRepository monsterRepository = new MonsterXmlRepository();
            foreach ( KeyValuePair<int, string> monsterNameId in monsterIds )
            {
                string monsterText = GetMonsterText(monsterNameId.Key);
                Monster monster = GetMonsterFromHtmlString(monsterText);
                monsterRepository.Save(monster);
            }
        }

        [Test]
        public void GetMonsterHtmlFiles()
        {
            const string monsterToSearchFor = "goblin";
            Dictionary<int, string> monsterIds = GetMonsterIds(monsterToSearchFor);
            foreach ( KeyValuePair<int, string> monster in monsterIds )
            {
                string monsterText = GetMonsterText(monster.Key);
                string path = string.Format("MonsterHtml\\{0}.htm", monster.Value);
                File.WriteAllText(path, monsterText);
                Console.Out.WriteLine("path = {0}", path);
            }
        }

        [Test]
        public void MonsterFileParsing()
        {
            string monsterHtml = GetMonsterHtml("Advanced Hobgoblin Warcaster");
            Monster monster = GetMonsterFromHtmlString(monsterHtml);
            Console.Out.WriteLine("monster = {0}", monster);
        }

        [Test]
        public void ProcessSpecificFile()
        {
            _monsterRepository = new MonsterXmlRepository();
//            These are all conjurations.  That will be a later version.
//            ImportHtmlAndSaveFile(@"MonsterHtml\Bloodstone Spider.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Conjured Beast (Rust Bag).htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Conjured Beast (Vermilion Bag).htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Conjured Critter (Gray Bag).htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Ebony Fly.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Golden Lion.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Ivory Goat of Travail.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Jade Macetail.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Marble Elephant.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Obsidian Steed.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Onyx Dog.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Pearl Sea Horse.htm");
//            ImportHtmlAndSaveFile(@"MonsterHtml\Tantron.htm");
        }

        [TearDown]
        public void TearDown()
        {
            DisposeOfBrowser();
        }

        private void DisposeOfBrowser()
        {
            if ( _ie != null )
            {
                _ie.Close();
                _ie.Dispose();
            }
        }

        private LinkCollection FindNextButton()
        {
            return _ie.Links.Filter(x => x.Text == "next");
        }

        private void GetBrowser()
        {
            Settings.Instance.MakeNewIeInstanceVisible = true;
            Settings.Instance.AutoMoveMousePointerToTopLeft = false;
            Settings.Instance.AutoCloseDialogs = true;
            if ( _ie == null )
            {
                _ie = new IE(false);
            }
        }

        private int GetExpectedMonsterCount()
        {
            string results = _ie.Link(Find.ById("creatures_link")).Text;
            int startIndex = results.IndexOf('(') + 1;
            int endIndex = results.IndexOf(')');
            return int.Parse(results.Substring(startIndex, endIndex - startIndex));
        }

        private Dictionary<int, string> GetMonsterIds(string monsterToSearchFor)
        {
            GetBrowser();
            NavigateToCompendium();
            SearchForMonster(monsterToSearchFor);
            WaitForSearchToComplete();
            Set100ResultsPerPage();
            int expectedMonsterCount = GetExpectedMonsterCount();
            SetSearchModeToMonsters();
            Dictionary<int, string> monsterIds = GetMonsterIds();
            if ( expectedMonsterCount != monsterIds.Count )
            {
                //Just try again
                Thread.Sleep(2000);
                monsterIds = GetMonsterIds();
            }
            Assert.AreEqual(expectedMonsterCount, monsterIds.Count);
            return monsterIds;
        }

        private Dictionary<int, string> GetMonsterIds()
        {
            MoveToPageOneOfSearchResults();
            Dictionary<int, string> monsterIds = GetMonsterIdsFromPage();
            while ( ThereAreMorePagesOfMonsters() )
            {
                MoveToNextPageOfMonsters();
                WaitForSearchToComplete();
                foreach ( KeyValuePair<int, string> monster in GetMonsterIdsFromPage() )
                {
                    monsterIds.Add(monster.Key, monster.Value);
                }
            }
            return monsterIds;
        }

        private Dictionary<int, string> GetMonsterIdsFromPage()
        {
            LinkCollection collection = _ie.Links.Filter(x => x.Url.Contains("monster.aspx"));
            Dictionary<int, string> ids = new Dictionary<int, string>();
            foreach ( Link link in collection )
            {
                ids.Add(int.Parse(link.Url.Split('=')[1]), link.Text);
            }
            return ids;
        }

        private string GetMonsterText(int monsterId)
        {
            GetBrowser();
            _ie.GoTo("http://www.wizards.com/dndinsider/compendium/monster.aspx?id=" + monsterId);
            if ( _ie.TextFields.Filter(x => x.Id == "email").Length == 1 )
            {
                TextField usernameText = _ie.TextField(x => x.Id == "email");
                usernameText.TypeText("get@your.own.com");
                TextField passwordText = _ie.TextField("password");
                passwordText.TypeText("password");
                Button loginButton = _ie.Button("InsiderSignin");
                loginButton.Click();
            }
            return _ie.Html;
        }

        private void ImportHtmlAndSaveFile(string fileName)
        {
            string monsterText = File.ReadAllText(fileName);
            Monster monster = GetMonsterFromHtmlString(monsterText);
            _monsterRepository.Save(monster);
        }

        private void MoveToNextPageOfMonsters()
        {
            FindNextButton()[0].Click();
        }

        private void MoveToPageOneOfSearchResults()
        {
            if ( _ie.Links.Filter(x => x.Url == "javascript:GotoPage(1);").Length >= 1 )
            {
                Link pageOne = _ie.Link(x => x.Url == "javascript:GotoPage(1);");
                pageOne.Click();
            }
        }

        private void NavigateToCompendium()
        {
            _ie.GoTo("http://www.wizards.com/dndinsider/compendium/database.aspx");
        }

        private void SearchForMonster(string monsterToSearchFor)
        {
            _ie.TextField(Find.ById("searchterm")).TypeText(monsterToSearchFor);
            _ie.Image(Find.ById("searchbutton")).Click();
            Thread.Sleep(200);
        }

        private void Set100ResultsPerPage()
        {
            if ( _ie.Links.Filter(x => x.Text == "100_per_page").Length > 0 )
            {
                _ie.Link(Find.ById("100_per_page")).Click();
                Thread.Sleep(200);
            }
        }

        private void SetSearchModeToMonsters()
        {
            _ie.Link(Find.ById("creatures_link")).Click();
            Thread.Sleep(200);
        }

        private bool ThereAreMorePagesOfMonsters()
        {
            return FindNextButton().Length > 0;
        }

        private void WaitForSearchToComplete()
        {
            DateTime maxWaitTime = DateTime.Now.AddSeconds(30);
            string monsterLink = _ie.Link(Find.ById("creatures_link")).Text;
            while ( !monsterLink.Contains("(") && DateTime.Now < maxWaitTime )
            {
                Thread.Sleep(200);
                monsterLink = _ie.Link(Find.ById("creatures_link")).Text;
            }
            if ( !monsterLink.Contains("(") )
            {
                throw new TimeoutException("The search did not return within the 30 second timeout.");
            }
        }

        private static string GetAttacksFromMonsterHtml(string monsterHtml)
        {
            MatchCollection matches = Regex.Matches(monsterHtml,
                                                    @"<P class=""flavor alt""><IMG .*?<B>(?<PowerName>[\w\s]*?)</B> \((?<Type>.*?)\).*?<B>(?<Keywords>.*?)</B>.*?</P>.*?<P class=flavorIndent>(?<Effect>.*?)</P>",
                                                    REGEX_OPTIONS);
            StringBuilder powers = new StringBuilder();
            foreach ( Match match in matches )
            {
                string name = match.Groups["PowerName"].Value;
                string type = Regex.Replace(match.Groups["Type"].Value, @"<IMG.*?(\d).+?gif"">", "$1 ").Trim().Replace("recharge", "Recharge:");
                string keywords = match.Groups["Keywords"].Value;
                string effect = match.Groups["Effect"].Value;

                powers.AppendFormat("{1} ({2}, {3}){0}{4}{0}{0}", Environment.NewLine, name, type, keywords, effect);
            }
            return powers.ToString().Trim();
        }

        private static string GetHighlightedNumericValueFromMonsterHtmlAsString(string monsterText, string keyword)
        {
            return GetMatchGroupValueFromPattern(monsterText, string.Format(@"<B>{0}</B>.+?(?<Value>[\+-]{{0,1}}\d+)", keyword), "Value");
        }

        private static string GetMatchGroupValueFromPattern(string monsterText, string matchPattern, string groupName)
        {
            Group matchGroupValueFromPattern = Regex.Match(monsterText, matchPattern, REGEX_OPTIONS).Groups[groupName];
            return matchGroupValueFromPattern.Success ? matchGroupValueFromPattern.Value : string.Empty;
        }

        private static Monster GetMonsterFromHtmlString(string monsterHtml)
        {
            Monster monster = new Monster(new Abilities(), new Defenses(), new HitPoints(), new Movement(), new Senses(), new Initiative());
            monster = SetMonsterHeaderInfoFromHtml(monster, monsterHtml);

            monster.AbilityStrength = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Str").TryParseToInt();
            monster.AbilityDexterity = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Dex").TryParseToInt();
            monster.AbilityConstitution = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Con").TryParseToInt();
            monster.AbilityIntelligence = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Int").TryParseToInt();
            monster.AbilityWisdom = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Wis").TryParseToInt();
            monster.AbilityCharisma = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Cha").TryParseToInt();

            monster.MovementStealth =
                GetMatchGroupValueFromPattern(monsterHtml, string.Format(@"{0}.+?(?<Value>[\+-]{{0,1}}\d+)", "Stealth"), "Value").TryParseToInt();

            monster.SensesPerception = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Senses").TryParseToInt();
            monster.SensesOther = GetMatchGroupValueFromPattern(monsterHtml, @"<B>Senses</B> Perception.+?\d+; (?<Senses>.+?)<BR>", "Senses");
            monster.InitiativeModifier = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Initiative").TryParseToInt();
            monster.HitPointsMaximum = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "HP").TryParseToInt();
            monster.DefenseArmorClass = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "AC").TryParseToInt();
            monster.DefenseFortitude = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Fortitude").TryParseToInt();
            monster.DefenseReflex = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Reflex").TryParseToInt();
            monster.DefenseWillpower = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Will").TryParseToInt();
            monster.MovementSpeed = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Speed").TryParseToInt();
            monster.MovementSpeedOther = GetMatchGroupValueFromPattern(monsterHtml, @"<B>Speed</B> \d+(?<OtherMovement>.*?)<", "OtherMovement");
            monster.SavingThrows = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Saving Throws").TryParseToInt();
            monster.ActionPoints = GetHighlightedNumericValueFromMonsterHtmlAsString(monsterHtml, "Action Points").TryParseToInt();

            monster.AttackPowers = GetAttacksFromMonsterHtml(monsterHtml);
            monster.SpecialQualities = GetSpecialQualitiesFromMonsterHtml(monsterHtml);
            return monster;
        }

        private static string GetMonsterHtml(string monsterName)
        {
            return
                File.ReadAllText(string.Format(@"C:\CodePlex\CampaignManager\trunk\CampaignManagerTests\bin\Debug\MonsterHtml\{0}.htm", monsterName));
        }

        private static string GetSpecialQualitiesFromMonsterHtml(string monsterHtml)
        {
            MatchCollection matches = Regex.Matches(monsterHtml,
                                                    "<P class=\"flavor alt\"><B>(?<Name>.*?)</B>(?<Trigger>.*?)</P>.*?<P class=flavorIndent>(?<Description>.*?)</P>",
                                                    REGEX_OPTIONS);
            StringBuilder powers = new StringBuilder();
            foreach ( Match match in matches )
            {
                string name = match.Groups["Name"].Value;
                string trigger = match.Groups["Trigger"].Value.Trim();
                string description = match.Groups["Description"].Value;
                powers.AppendFormat("{0} {1}", name, trigger);
                powers.AppendLine();
                powers.AppendFormat("    {0}", description);
                powers.AppendLine();
                powers.AppendLine();
            }
            return powers.ToString().Trim();
        }

        private static string MakeFileNameSafe(string fileName)
        {
            return Regex.Replace(fileName, "[\\/?*:<>|\"]", string.Empty);
        }

        private static string RemoveMarkUp(string equipment)
        {
            return Regex.Replace(equipment, "<.*?>", string.Empty).Replace(".", string.Empty).Trim();
        }

        private static Monster SetMonsterHeaderInfoFromHtml(Monster monster, string monsterText)
        {
            const string headerSearchPattern =
                @"<H1 class=monster><SPAN class=level>Level (?<Level>\d+) (?<Role>.+)<SPAN class=xp>.*</SPAN>(?<Name>.*)<SPAN class=type>(?<Size>.+?) (?<Keywords>.*)</SPAN>.*?</H1>";
            Match match = Regex.Match(monsterText, headerSearchPattern, REGEX_OPTIONS);
            monster.Name = MakeFileNameSafe(match.Groups["Name"].Value);
            monster.Level = int.Parse(match.Groups["Level"].Value);
            string role =
                RemoveMarkUp(match.Groups["Role"].Value.Replace("Elite", string.Empty).Replace("(Leader)", string.Empty).Replace("Solo", string.Empty));
            MonsterRole monsterRole = MonsterRole.Soldier;
            if ( Enum.IsDefined(typeof ( MonsterRole ), role) )
            {
                monsterRole = (MonsterRole) Enum.Parse(typeof ( MonsterRole ), role.Split(' ')[0]);
            }
            monster.Role = monsterRole;
            CreatureSize creatureSize = CreatureSize.Medium;
            if ( Enum.IsDefined(typeof ( CreatureSize ), match.Groups["Size"].Value) )
            {
                creatureSize = (CreatureSize) Enum.Parse(typeof ( CreatureSize ), match.Groups["Size"].Value);
            }
            monster.CreatureSize = creatureSize;
            monster.Keywords = match.Groups["Keywords"].Value.Trim().ToListOfStrings();
            monster.Elite = Regex.IsMatch(monsterText, "<H1.*Elite.*</H1>", REGEX_OPTIONS);
            monster.Leader = Regex.IsMatch(monsterText, "<H1.*Leader.*</H1>", REGEX_OPTIONS);
            monster.Solo = Regex.IsMatch(monsterText, "<H1.*Solo.*</H1>", REGEX_OPTIONS);
            string equipment = GetMatchGroupValueFromPattern(monsterText, "<B>Equipment</B>: (?<Equipment>.*?)</P>", "Equipment");
            monster.Equipment = RemoveMarkUp(equipment);
            return monster;
        }

        private static void WriteMonsterIdsFromPageToOutputWindow(Dictionary<int, string> monsterIds)
        {
            foreach ( KeyValuePair<int, string> monsterId in monsterIds )
            {
                Console.Out.WriteLine(monsterId);
            }
        }
    }
}